#pragma once
#include <QScopedPointer>
#include <QString>
#include <vector>
#include <memory>

namespace AES
{
using Byte = unsigned char;

enum class Strength {
    AES128,
    AES192,
    AES256
};

//password used in authority (since 20-05-07)
std::vector<Byte> uniformCodeForAuthority();
//password used in encryption / decryption operations now (since 21-12-21)
std::vector<Byte> uniformCodeV3();
//password used in encryption / decryption operations in version 2 (since 19-02-25)
std::vector<Byte> uniformCodeV2();
//password used in encryption / decryption operations in version 1 (since 18-03-27)
std::vector<Byte> uniformCodeV1();

// used for key generator
std::vector<Byte> keyGeneratorCode();

bool calculateSHA1(const std::vector<Byte> &text, std::vector<Byte> &result);

bool encryptFile(const QString &file,
                 const QString &targetFile,
                 const std::vector<Byte> &pwd = uniformCodeV3());

bool decryptFile(const QString &file,
                 const QString &targetFile,
                 const std::vector<Byte> &pwd = {});

bool mayBeCipher(const QString &file);

bool mayBeCipher(const void *buf, int nBytes);

class Encryptor
{
public:
    Encryptor(Strength s = Strength::AES128,
              const std::vector<Byte> &iv = std::vector<Byte>());
    virtual ~Encryptor() = default;
    void setStrength(Strength s);
    Strength strength() const;
    void setInitialVector(const std::vector<Byte> &iv);
    std::vector<Byte> initialVector() const;
    void setPlaintTextBytes(unsigned long bytes);
    void setCBCChainingMode(bool on);
    bool encrypt(const void *buffer,
                 unsigned long nBytes,
                 const std::vector<Byte> &pwd,
                 std::vector<Byte> &out);
    bool encrypt(const void *buffer,
                 unsigned long nBytes,
                 const std::vector<Byte> &pwd,
                 void *out,
                 unsigned long outBytes);
    bool decrypt(const void *buffer,
                 unsigned long nBytes,
                 const std::vector<Byte> &pwd,
                 std::vector<Byte> &out,
                 bool resetLength = true);
    bool decrypt(const void *buffer,
                 unsigned long nBytes,
                 const std::vector<Byte> &pwd,
                 void *out,
                 unsigned long outBytes,
                 unsigned long *originBytes = nullptr);
protected:
    Strength _strength = Strength::AES128;
    std::vector<Byte> _iv;
    unsigned long _plainTextBytes = 0;
    bool _useCBCChainingMode = true;
};

/*
    CipherFileFormat : Header + Block1 + Block2 +... +BlockN
    Header : 8Bytes (magic number) +
             4Bytes (verification flag) +
             20Bytes(SHA1 value of all the blocks, only used when verification flag is 1)
    Block  : 4Bytes (size of plain data) +
             4Bytes (size of ciphered data) +
             Variable Bytes (ciphered data)
*/
class CipheredFileWriter
{
public:
    CipheredFileWriter(const QString &file,
                       const std::vector<Byte> &pwd = AES::uniformCodeV3());
    ~CipheredFileWriter();
    bool writeData(const void *buffer, unsigned long bytes);
    bool writeData(const std::vector<Byte> &data);
    bool writeVerification();
    void close();

private:
    struct Impl;
    QScopedPointer<Impl> _pImpl;
};

class CipheredFileReader
{
public:
    CipheredFileReader(const QString &file,
                       const std::vector<Byte> &pwd = {});
    ~CipheredFileReader();
    bool isValid();
    bool readAll(std::vector<Byte> &data);
    void close();

private:
    struct Impl;
    QScopedPointer<Impl> _pImpl;
};

class CipheredMemBlock
{
    std::vector<Byte> _memBlock;
    static int fixedSize();
public:
    CipheredMemBlock() = default;
    CipheredMemBlock(const CipheredMemBlock &) = default;
    CipheredMemBlock(CipheredMemBlock &&) = default;
    CipheredMemBlock(const void *addr, uint32_t bytes);
    static std::unique_ptr<CipheredMemBlock> fromData(const void *data,
                                                      uint32_t dataBytes,
                                                      const std::vector<Byte> &pwd = {},
                                                      bool chainingMode = false);
    bool toData(std::vector<Byte> &data, const std::vector<Byte> &pwd = {});
    bool toData(void *data, uint bytes, const std::vector<Byte> &pwd = {},
                uint *pBytesCopied = nullptr);
    bool toDataFirstNBytes(uint bytes, std::vector<Byte> &data,
                           const std::vector<Byte> &pwd = {});
    std::vector<Byte> &memBlock();
    bool isValid() const;
    int getVersion() const;

protected:
    uint getPlainTextBytes() const;
    uint getCipherTextBytes() const;
};

}